#include <assert.h>
#include <stdio.h>

#include "mreq.h"
#include "uarch/settings.h"
#include "uarch/sim.h"

extern Simulator Sim;

using namespace std;

const char * Mreq::message_t_str[MREQ_MESSAGE_NUM] = {

    "NOP_INST",

    // MIPS specific
    "SYNC",
    "LL",
    "SC",

    // Set stalls
    "QUEUE_MATCH_STALL",
    "SET_STALL",
    "REPLACEMENT_STALL",
    "BEING_REPLACED_STALL",
    "STALL_MSHR",

    // Entry stalls
    "PERMISSION_STALL",
    "TRANS_STALL",
    "INV_DIR_STALL",
    "DOWNGRAD_STALL",
    "STALL_SET_XFER",

    // Stall retries for history
    "STALL_SET_RETRY",
    "STALL_ENTRY_RETRY",

    // directory requests (MOSI only/ TODO: Fix)
    "D_FWD_GETX",
    "D_FWD_GETS",

    // directory response
    "WRITEBACK_ACK",

    // new from MOSI
    "GETX_FAIL",  // Extremely, rare case.  See MOSI_cache.cpp for notes.

    // new for cache_to_cache sharing
    "FWD_GETS_S",
    "FWD_GETS_RETRY",
    "FWD_GETS_FAIL",

    // New for MSI
    "C_DATA_S",
    "C_DATA_E",
    "C_DATA_M",
    "C_EVICT_ACK",
    "C_EVICT_ACK_DATA_M",
    "C_GETIS",
    "C_GETIM",
    "C_GETSM",
    "C_GETOM",
    "C_INV_ACK",
    "C_PUTE",
    "C_PUTE_OLD",
    "C_PUTO",
    "C_PUTO_OLD",
    "C_PUTM",
    "C_PUTM_OLD",
    "C_PUTS",
    "C_UNBLOCK_S",
    "C_UNBLOCK_S_DIRTY",
    "C_UNBLOCK_E",
    "C_UNBLOCK_O",
    "C_UNBLOCK_M",
    "C_UNBLOCK_CONVERT",
    "C_UNBLOCK_NO_CONVERT",
    "C_REMAP_INSERT",
    "C_REMAP_REMOVE",

    "D_EVICT",
    "D_FWD_GETIS",
    "D_FWD_GETIM",
    "D_INV",
    "D_INV_ACK_CNT",
    "D_PUTM",
    "D_REMAP_INSERT",
    "D_REMAP_REMOVE",
    "D_WB_ACK",
    "D_DOWNGRADE",
    "D_UPGRADE",

    "M_UNBLOCK_WB",

    "H_REPLACEMENT",
    "H_DOWNGRADE",

    "SUPPLY_DATA_S",
    "SUPPLY_DATA_E",
    "SUPPLY_DATA_M",

    "CRUISE_MISSILE",

    "MREQ_INVALID"
};

/***************
 * Constructor.
 ***************/
Mreq::Mreq (message_t msg, paddr_t addr, Preq *preq, ModuleID src_mid, ModuleID dest_mid, ModuleID fwd_mid, int acks)
{
    this->msg = msg;
    this->addr = addr & ((~0x0) << Sim.settings.cache_line_size_log2);
    this->preq = preq;
    this->src_mid = src_mid;
    this->dest_mid = dest_mid;
    this->fwd_mid = fwd_mid;
    this->INV_ACK_count = acks;
    this->req_time = Global_Clock;
//    this->cruise_missile = false;
//    this->cm_targets = NULL;
//    this->cm_request = NULL;
    this->stalled = false;
    this->module_port_resolve_time = 0;

    this->is_no_net_msg = false;
}

Mreq::~Mreq(void)
{
    //TODO: May cause double free
    /**
    if (cm_targets)
    {
        delete cm_targets;
        cm_targets = NULL;
    }
    */
}

bool Mreq::is_query (void)
{
    return (msg == C_GETIS ||
            msg == C_GETIM ||
            msg == C_GETSM ||
            msg == C_GETOM ||
            msg == C_PUTE ||
            msg == C_PUTO ||
            msg == C_PUTM);
}

bool Mreq::is_put (void)
{
    return (msg == C_PUTE ||
            msg == C_PUTO ||
            msg == C_PUTM);
}

bool Mreq::is_data_msg (void)
{
    return (msg == C_DATA_S ||
            msg == C_DATA_E ||
            msg == C_DATA_M ||
            msg == C_EVICT_ACK_DATA_M ||
            msg == C_PUTO ||
            msg == C_PUTM ||
            msg == D_PUTM ||
            msg == C_UNBLOCK_S_DIRTY);
}

bool Mreq::is_to_client (void)
{
    return (msg == C_DATA_S ||
            msg == C_DATA_E ||
            msg == C_DATA_M ||
            msg == C_INV_ACK ||
            msg == D_DOWNGRADE ||
            msg == D_UPGRADE ||
            msg == D_EVICT ||
            msg == D_FWD_GETIS ||
            msg == D_FWD_GETIM ||
            msg == D_INV ||
            msg == D_INV_ACK_CNT ||
            msg == D_WB_ACK);
}

bool Mreq::is_bcast_hack (void)
{
    return (msg == D_DOWNGRADE ||
            msg == D_UPGRADE ||
            msg == D_EVICT ||
            msg == D_FWD_GETIS ||
            msg == D_FWD_GETIM ||
            msg == D_INV ||
            msg == D_INV_ACK_CNT ||
            msg == D_WB_ACK);
}

bool Mreq::is_to_directory (void)
{
    return (msg == C_EVICT_ACK ||
            msg == C_EVICT_ACK_DATA_M ||
            msg == C_PUTE ||
            msg == C_PUTO ||
            msg == C_PUTM ||
            msg == C_PUTS ||
            msg == C_UNBLOCK_S ||
            msg == C_UNBLOCK_S_DIRTY ||
            msg == C_UNBLOCK_E ||
            msg == C_UNBLOCK_O ||
            msg == C_UNBLOCK_M ||
            msg == C_UNBLOCK_CONVERT ||
            msg == C_UNBLOCK_NO_CONVERT ||
            msg == M_UNBLOCK_WB);
}

bool Mreq::is_response_msg (void)
{
    return (msg == C_DATA_S ||
            msg == C_DATA_E ||
            msg == C_DATA_M ||
            msg == C_EVICT_ACK ||
            msg == C_EVICT_ACK_DATA_M ||
            msg == C_INV_ACK ||
            msg == C_UNBLOCK_S ||
            msg == C_UNBLOCK_S_DIRTY ||
            msg == C_UNBLOCK_E ||
            msg == C_UNBLOCK_O ||
            msg == C_UNBLOCK_M);    
}

bool Mreq::requires_dir_processing (void)
{
    return (msg == D_INV || 
            msg == D_EVICT ||
            msg == D_FWD_GETIM ||
            msg == D_FWD_GETIS ||
            msg == D_DOWNGRADE);
}

bool Mreq::requires_inv_dir (void)
{
    return (msg == D_INV || 
            msg == D_EVICT ||
            msg == D_FWD_GETIM);
}

void Mreq::add_to_history (Mreq * request, const char *msg)
{
    if (preq == NULL)
        return;

    if (!request->is_to_directory ())
        preq->add_to_history (request, msg);
}

void Mreq::print_msg (ModuleID mid, const char *add_msg)
{
    //TODO: convert fprintfs to c++-ishy output
    fprintf (stderr, "GC %8llu ", (unsigned long long)Global_Clock);
    print_id ("node", mid);
    print_id ("src", src_mid);
    print_id ("dest", dest_mid);
    print_id ("fwd", fwd_mid);
    fprintf (stderr, "0x%8llx req time: %8lld ", (long long int)addr, (long long int)req_time);
    fprintf (stderr, " %20s: %s\n", Mreq::message_t_str[msg], add_msg);
}

void Mreq::preq_print_mreq (timestamp_t t, const char *add_msg)
{
    //TODO: convert fprintfs to c++-ishy output
    fprintf (stderr, "Time %8llu ", (unsigned long long)t);
    print_id ("src", src_mid);
    print_id ("dest", dest_mid);
    print_id ("fwd", fwd_mid);
    fprintf (stderr, "0x%8llx req time: %8lld ", (long long int)addr, (long long int)req_time);
    fprintf (stderr, " %20s: %s\n", Mreq::message_t_str[msg], add_msg);
}

void Mreq::dump ()
{
    //TODO: convert fprintfs to c++-ishy output
    fprintf (stderr, "GC %8llu ", (unsigned long long)Global_Clock);
    print_id ("src", src_mid);
    print_id ("dest", dest_mid);
    print_id ("fwd", fwd_mid);
    fprintf (stderr, "0x%8llx req time: %8lld %20s\n", 
             (long long int)addr, (long long int)req_time, Mreq::message_t_str[msg]);
}

int Mreq::Serialize(Mreq* request, unsigned char *req_copy)
{
    fatal_error("shouldn't be called for single LP!");
}

Mreq* Mreq::Deserialize(unsigned char *req_copy)
{
    fatal_error("shouldn't be called for single LP!");
}
